Skip to content

Add audit command to scan packages for security advisories#19

Merged
exo-nikita merged 8 commits into
masterfrom
claude/implement-audit-command-BnwJz
May 18, 2026
Merged

Add audit command to scan packages for security advisories#19
exo-nikita merged 8 commits into
masterfrom
claude/implement-audit-command-BnwJz

Conversation

@exo-nikita
Copy link
Copy Markdown
Collaborator

Summary

This PR adds a new audit command to the stasis CLI that scans packages from lockfiles and bundles for security vulnerabilities using the npm advisories API.

Key Changes

  • New audit module (src/audit.js): Core functionality for auditing packages

    • collectPackagesFromFile(): Extracts package name/version from stasis lockfiles or brotli-compressed bundles
    • collectPackages(): Aggregates packages from multiple files with deduplication
    • flattenAdvisories(): Transforms advisory results into sortable rows, ordered by severity then package name
    • formatTable(): Generates aligned tabular output for advisory reports
    • audit(): Main function that collects packages and queries npm advisories API
    • printAuditReport(): Formats and outputs audit results to stdout/stderr
  • CLI integration (bin/stasis.js): Added stasis audit command

    • Accepts one or more file paths (lockfiles or bundles)
    • Exits with code 0 if no vulnerabilities found, 1 if advisories exist
    • Provides helpful error messages for missing files or invalid formats
  • Comprehensive test suite (tests/audit.test.js): 173 lines of tests covering

    • Package extraction from lockfiles and bundles
    • Deduplication logic (exact matches and cross-file duplicates)
    • Advisory sorting and formatting
    • CLI integration and error handling

Implementation Details

  • Supports both JSON lockfiles and brotli-compressed bundles transparently
  • Deduplicates packages by name+version across multiple input files
  • Sorts advisories by severity (critical → info) then by package name for consistent output
  • Cleans environment variables in CLI tests to avoid interference from stasis configuration

https://claude.ai/code/session_012AEjpn3Kc2i8WfHw5ZgPEv

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new stasis audit CLI command intended to collect package versions from stasis artifacts and query npm security advisories.

Changes:

  • Added src/audit.js for package extraction, advisory flattening, table formatting, and reporting.
  • Added stasis audit command wiring in the CLI.
  • Added audit-focused tests for package collection, formatting, and basic CLI errors.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.

File Description
bin/stasis.js Adds usage text and command dispatch for audit.
src/audit.js Implements package collection, npm advisory lookup, and audit report formatting.
tests/audit.test.js Adds tests for audit helpers and basic CLI error handling.
Comments suppressed due to low confidence (2)

src/audit.js:30

  • stasis bundles generated by State.sourceData contain loaded source files, formats, and imports, but they do not persist package metadata or package.json files unless those package.json files were themselves imported. As a result, auditing a normal generated bundle will collect zero packages and falsely print "No advisories found", which defeats the bundle-audit path.
  for (const [path, source] of Object.entries(json.sources)) {
    if (!isPackageJsonPath(path)) continue
    const { name, version } = JSON.parse(source)
    if (name && version) out.push({ name, version })

src/audit.js:36

  • The lockfile shape check is loose enough to accept malformed objects such as { version: 0, sources: {} } that do not contain the required modules section, leading to a successful audit with zero packages. Since generated lockfiles always include modules, this should reject incomplete lockfiles instead of treating them as clean.
function isLockfileShape(json) {
  return Boolean(json && (json.modules || (json.sources && Object.values(json.sources).every((v) => v && typeof v === 'object' && 'name' in v))))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/audit.test.js Outdated
Comment thread src/audit.js Outdated
Comment thread src/audit.js
Comment thread src/audit.js
Comment thread src/audit.js
Comment thread src/audit.js Outdated
Comment thread src/audit.js
@exo-nikita exo-nikita force-pushed the claude/implement-audit-command-BnwJz branch 2 times, most recently from 2c5125c to fd4d161 Compare May 18, 2026 14:45
@ChALkeR ChALkeR requested a review from Copilot May 18, 2026 14:57
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

Comment thread src/audit.js Outdated
Comment thread src/audit.js Outdated
claude added 8 commits May 18, 2026 15:59
Collects package name/version pairs from stasis lockfiles and brotli
bundles, deduplicates them, queries the npm bulk advisories endpoint,
and prints results as a table sorted by severity.
- Skip workspace/first-party modules so private package names are not
  sent to the public npm advisories endpoint.
- Show installed version(s) affected by each advisory by joining via
  semver.satisfies against vulnerable_versions.
- Add a 30s timeout (AbortSignal.timeout) to the registry request and
  wrap non-2xx / network failures in a clear Error with status + body.
- Cover the audit() path with tests that stub globalThis.fetch
  (happy path body + report, and 503 error wrapping).
- parseFile: sniff the first byte to route lockfile vs bundle so a
  parse failure surfaces the correct diagnostic instead of swallowing
  the real error and reporting the wrong format.
- flattenAdvisories: drop rows where no installed version matches the
  advisory's vulnerable range (avoids false positives, including bogus
  non-zero exit codes from such rows).
- collectPackages: sort via localeCompare + semver.compare (matches the
  style already used in apis/npm/index.js).
- formatTable: collapse newlines in cells so titles containing \n don't
  break the box layout.
- Add 'none' to SEVERITY_ORDER (npm emits it occasionally).
- Drop redundant truthy check in apis/npm/index.js asserts.
- Tests: dedup doesn't collapse different packages at the same version;
  bundle and lockfile diagnostics route correctly; zero-match advisory
  drop; network/abort error wrapping.
- parseFile now sniffs the bundle shape after brotli decompression and
  routes to Bundle.parseCode or Bundle.parseResources accordingly.
  Resource bundles (stasis.resources.br) carry the same per-module
  name/version metadata as code bundles, so they're auditable too.
- Wrap readFileSync ENOENT in a clean 'File not found: <path>' error
  instead of letting Node's raw stack escape to the CLI.
- parseFile: the JSON.parse used to disambiguate code-vs-resource
  bundles was outside the try/catch — a brotli-valid but JSON-corrupt
  input leaked a raw SyntaxError. Move it inside the catch so all
  bundle parse failures surface 'Failed to parse stasis bundle'.
- ENOENT throw now preserves { cause }, matching the sibling read-
  error throw.
- cell() regex broadened to include bare CR and U+2028/U+2029 so
  Unicode line/paragraph separators in advisory titles can't break
  the box layout (written as escape sequences to survive editor
  whitespace-trim).
- printAuditReport now hints 'No node_modules entries found in the
  input files' when the caller passed files but they yielded zero
  audit-eligible packages — silent 0-scan is confusing.
@exo-nikita exo-nikita force-pushed the claude/implement-audit-command-BnwJz branch from 14d180e to e0c94bd Compare May 18, 2026 15:59
@exo-nikita exo-nikita merged commit 6802703 into master May 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants